package org.apache.struts2.interceptor;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

@SuppressWarnings("serial")
public class AnnotationCookieInterceptor extends CookieInterceptor implements PreResultListener {
	private static final Logger LOG = LoggerFactory.getLogger(AnnotationCookieInterceptor.class);
	private EscapeCharacterProcessor escapeCharacterProcessor = new EscapeCharacterProcessor();

	@Override
	protected void injectIntoCookiesAwareAction(Object action, @SuppressWarnings("unchecked") Map cookiesMap) {
		super.injectIntoCookiesAwareAction(action, cookiesMap);
		Map<String, Cookie> annotatedProperties = findCookieAnnotatedProperties(action);
		for (String property : annotatedProperties.keySet()) {
			try {
				String value = annotatedProperties.get(property).name();
				BeanUtils.setProperty(action, property, escapeCharacterProcessor.unescape((String) cookiesMap.get(value)));
			} catch (Exception e) {
				LOG.warn("Error setting cookie value into action property ", e, action.toString(), property);
			}
		}
	}

	private Map<String, Cookie> findCookieAnnotatedProperties(Object action) {
		Map<String, Cookie> annotatedPropeties = new HashMap<String, Cookie>();
		Class<?> targetClass = action.getClass();
		do {
			Field[] fields = targetClass.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Cookie cookie = checkField(fields[i], action);
				if (cookie != null) {
					annotatedPropeties.put(fields[i].getName(), cookie);
				}
			}
			targetClass = targetClass.getSuperclass();
		} while (targetClass != null && targetClass != Object.class);
		return annotatedPropeties;
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		invocation.addPreResultListener(this);
		return preProcessAndInvoke(invocation);
	}

	private void createCookie(Cookie cookie, String propertyValue, HttpServletResponse response) {
		String value = cookie.name();
		javax.servlet.http.Cookie saveCookie = new javax.servlet.http.Cookie(value, escapeCharacterProcessor.escape(propertyValue));
		if (StringUtils.isNotBlank(cookie.path())) {
			saveCookie.setPath(cookie.path());
		}
		if (saveCookie.getValue() == null) {
			saveCookie.setMaxAge(0);
		} else {
			saveCookie.setMaxAge(cookie.timeToLive());
		}
		response.addCookie(saveCookie);

	}

	private Cookie checkField(Field field, Object bean) {
		return field.getAnnotation(Cookie.class);
	}

	public String preProcessAndInvoke(ActionInvocation invocation) throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		Map<String, String> cookiesMap = new LinkedHashMap<String, String>();
		javax.servlet.http.Cookie cookies[] = request.getCookies();
		if (cookies != null) {
			for (int a = 0; a < cookies.length; a++) {
				String name = cookies[a].getName();
				String value = cookies[a].getValue();
				cookiesMap.put(name, value);
			}
		}
		injectIntoCookiesAwareAction(invocation.getAction(), cookiesMap);
		return invocation.invoke();
	}

	public void beforeResult(ActionInvocation invocation, String resultCode) {
		Object action = invocation.getAction();
		Map<String, Cookie> annotatedProperties = findCookieAnnotatedProperties(action);
		for (String property : annotatedProperties.keySet()) {
			try {
				Object propertyValue = BeanUtils.getProperty(action, property);
				HttpServletResponse response = (HttpServletResponse) invocation.getInvocationContext().get(
						StrutsStatics.HTTP_RESPONSE);
				createCookie(annotatedProperties.get(property), (String) propertyValue, response);
			} catch (Exception e) {
				LOG.warn("Error creating cookie from action property", e, action.toString(), property);
			}
		}
	}

	private class EscapeCharacterProcessor {
		private Map<String, String> escapedUnescapedCharacters;

		private EscapeCharacterProcessor() {
			escapedUnescapedCharacters = new HashMap<String, String>();
			escapedUnescapedCharacters.put("\\|", "@");
		}

		/**
		 * Multiple String Building. Bad Performance
		 * 
		 * @param unescaped
		 * @return
		 */
		private String escape(String unescaped) {
			if (unescaped == null) {
				return null;
			}
			String tempString = unescaped;
			for (String escapeCharacter : escapedUnescapedCharacters.keySet()) {
				tempString = tempString.replaceAll(escapedUnescapedCharacters.get(escapeCharacter), escapeCharacter);
			}
			return tempString;
		}

		/**
		 * Multiple String Building. Bad Performance
		 * 
		 * @param unescaped
		 * @return
		 */
		private String unescape(String escaped) {
			if (escaped == null) {
				return null;
			}
			String tempString = escaped;
			for (String escapeCharacter : escapedUnescapedCharacters.keySet()) {
				tempString = tempString.replaceAll(escapeCharacter, escapedUnescapedCharacters.get(escapeCharacter));
			}
			return tempString;
		}
	}
}
